package com.zzzzzz.topic.web;

import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import org.apache.shiro.authz.annotation.RequiresUser;
import org.neo4j.helpers.collection.IteratorUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import com.zzzzzz.account.model.User;
import com.zzzzzz.account.model.UserGraph;
import com.zzzzzz.account.service.UserGraphService;
import com.zzzzzz.account.service.UserService;
import com.zzzzzz.common.util.Constants;
import com.zzzzzz.common.util.Rels;
import com.zzzzzz.common.util.StringPool;
import com.zzzzzz.common.util.Util;
import com.zzzzzz.common.util.Validator;
import com.zzzzzz.core.exception.NoSuchTopicException;
import com.zzzzzz.core.exception.ZzzzzzException;
import com.zzzzzz.core.repository.TopicRepository;
import com.zzzzzz.core.repository.UserRepository;
import com.zzzzzz.core.web.BaseController;
import com.zzzzzz.topic.model.AddTopicVo;
import com.zzzzzz.topic.model.Comment;
import com.zzzzzz.topic.model.CommentBo;
import com.zzzzzz.topic.model.CommentJsonBo;
import com.zzzzzz.topic.model.CommentJsonListBo;
import com.zzzzzz.topic.model.CommentAddJsonBo;
import com.zzzzzz.topic.model.CommentReplyJsonBo;
import com.zzzzzz.topic.model.EditTopicVo;
import com.zzzzzz.topic.model.SimpleTopicBo;
import com.zzzzzz.topic.model.Topic;
import com.zzzzzz.topic.model.TopicBo;
import com.zzzzzz.topic.model.TopicGraph;
import com.zzzzzz.topic.model.TopicJsonBo;
import com.zzzzzz.topic.model.TopicJsonListBo;
import com.zzzzzz.topic.service.CommentService;
import com.zzzzzz.topic.service.TopicService;

@Controller
public class TopicController  extends BaseController {
	
	@Resource
	private UserService userService;

    @Autowired
    private UserRepository userRepository;

	@Resource
    private UserGraphService userGraphService;
	
	@Resource
	private TopicService topicService;

    @Autowired
    private TopicRepository topicRepository;

	@Resource
	private CommentService commentService;

	@Resource
	private MessageSource messageSource;

	@RequiresUser
	@RequestMapping(value = "/topic/add", method = RequestMethod.GET)
	public String viewAddPage(Model model, @ModelAttribute AddTopicVo addTopicVo) {
		model.addAttribute("cur", "add");
		return "topic/add";
	}

	@RequiresUser
	@RequestMapping(value = "/topic/add", method = RequestMethod.POST)
	public String processAdd(Model model, @ModelAttribute AddTopicVo addTopicVo, BindingResult result, HttpServletRequest request) throws Exception {
		try {
			String category = request.getParameter("category");
			addTopicVo.setCategory(category);
			Topic topic = topicService.add(addTopicVo);

			// save to neo4j database
			TopicGraph topicGraph = new TopicGraph();
			topicGraph.setTopicId(topic.getTopicId());
			topicRepository.save(topicGraph);
			
			return "redirect:/explore";
			
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			throw e; // handle by Spring
		}
	}

	@RequiresUser
	@RequestMapping(value="/topic/edit/{id}", method=RequestMethod.GET)
	public String viewEditPage(Model model, @ModelAttribute EditTopicVo editTopicVo, @PathVariable String id) throws Exception {
		try{
			Topic topic = topicService.findById(id);
			User currentUser = userService.findCurrentUser();
			if(!topic.getUserId().equalsIgnoreCase(currentUser.getUserId())) {
				return "exception/permission_denied";
			}
			editTopicVo.setTopicId(topic.getTopicId());
			editTopicVo.setThumbnailId(topic.getThumbnailId());
			editTopicVo.setDescription(topic.getDescription());
			editTopicVo.setCategory(topic.getCategory());
			
		}catch(Exception e) {
			if(e instanceof NoSuchTopicException) {
				return "exception/404";
			}else {
				logger.error(e.getMessage(), e);
				throw e; // handle by Spring
			}
		}
		return "topic/edit";
	}
	
	@RequiresUser
	@RequestMapping(value = "/topic/edit", method = RequestMethod.POST)
	public String processEdit(Model model, @ModelAttribute EditTopicVo editTopicVo, BindingResult result, HttpServletRequest request) throws Exception {
		try {
			Topic topic = topicService.findById(editTopicVo.getTopicId());
			User currentUser = userService.findCurrentUser();
			if(!topic.getUserId().equalsIgnoreCase(currentUser.getUserId())) {
				return "exception/permission_denied";
			}
			editTopicVo.updatePo(topic);
			topicService.edit(topic);
			
			return "redirect:/topic/" + topic.getTopicId();
			
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			throw e; // handle by Spring
		}
	}
	

	@RequestMapping(value="/topic/delete/{id}", method=RequestMethod.GET)
	public String deleteTopic(Model model, @PathVariable String id, HttpServletRequest request) throws Exception {
		try{
			User currentUser = userService.findCurrentUser();
			Topic topic = topicService.findById(id);
			if(!topic.getUserId().equalsIgnoreCase(currentUser.getUserId())) {
				return "exception/permission_denied";
			}
			topicService.editStatus(id, 1);
		}catch(Exception e) {
			if(e instanceof NoSuchTopicException) {
				return "exception/404";
			}else {
				logger.error(e.getMessage(), e);
				throw e; // handle by Spring
			}
		}
		return "redirect:/profile";
	}
	
	@RequestMapping(value="/topic/{id}", method=RequestMethod.GET)
	public String viewTopicDetailPage(Model model, @PathVariable String id, HttpServletRequest request) throws Exception {
		try{
			Topic topic = topicService.findById(id);
			if(topic != null) {
				User creator = userService.findById(topic.getUserId());
				TopicBo topicBo = new TopicBo(topic);
				topicBo.setUserName(creator.getUsername());
				topicBo.setUserThumbnailId(creator.getThumbnailId());
				UserGraph creatorGraph = userRepository.findByPropertyValue("userId", creator.getUserId());
				if(isSignedIn()) {
					User currentUser = userService.findCurrentUser();
					UserGraph currentUserGraph = userRepository.findByPropertyValue("userId", currentUser.getUserId());
					boolean isFollowing = userGraphService.hasRelationship(currentUserGraph, creatorGraph, Rels.FOLLOW.name());
					TopicGraph topicGraph = topicRepository.findByPropertyValue("topicId", id);
	    			boolean isLiking = userGraphService.hasRelationship(currentUserGraph, topicGraph, Rels.LIKE.name());
					model.addAttribute("isFollowing", isFollowing);
					model.addAttribute("isLiking", isLiking);
				}
				int pStart = 0;
				int pOffset = 10;
				boolean pAscending = false;
				int cStart = 0;
				int cOffset = 3;
				boolean cAscending = false;
				List<CommentBo> parentCommentBos = this.getParentCommentBos(id, pStart, pOffset, pAscending, cStart, cOffset, cAscending);
				model.addAttribute("parentCommentBos", parentCommentBos);

				Long parentCountL = commentService.findCountByTopicId(id);
				// assume count less than Integer.MAX_VALUE
				int parentCount = Util.safeLongToInt(parentCountL);
				model.addAttribute("parentCount", parentCount);
				model.addAttribute("topicBo", topicBo);

				TopicGraph topicGraph = topicRepository.findByPropertyValue("topicId", id);
				int numOfUsersWhoLike = getNumOfUsersWhoLike(topicGraph);
		    	long numOfComments = commentService.findCountByTopicId(id);
		    	model.addAttribute("numOfUsersWhoLike", numOfUsersWhoLike);
		    	model.addAttribute("numOfComments", numOfComments);
			}
		}catch(Exception e) {
			if(e instanceof NoSuchTopicException) {
				return "exception/404";
			}else {
				logger.error(e.getMessage(), e);
				throw e; // handle by Spring
			}
		}
		return "topic/view";
	}
	
	@RequestMapping(value="/topic_popup/{id}", method=RequestMethod.GET)
	public String viewTopicDetailPopup(Model model, @PathVariable String id, HttpServletRequest request) throws Exception {
		try{
			Topic topic = topicService.findById(id);
			if(topic != null) {
				User creator = userService.findById(topic.getUserId());
				TopicBo topicBo = new TopicBo(topic);
				topicBo.setUserName(creator.getUsername());
				topicBo.setUserThumbnailId(creator.getThumbnailId());
				UserGraph creatorGraph = userRepository.findByPropertyValue("userId", creator.getUserId());
				if(isSignedIn()) {
					User currentUser = userService.findCurrentUser();
					UserGraph currentUserGraph = userRepository.findByPropertyValue("userId", currentUser.getUserId());
					boolean isFollowing = userGraphService.hasRelationship(currentUserGraph, creatorGraph, Rels.FOLLOW.name());
					TopicGraph topicGraph = topicRepository.findByPropertyValue("topicId", id);
	    			boolean isLiking = userGraphService.hasRelationship(currentUserGraph, topicGraph, Rels.LIKE.name());
					model.addAttribute("isFollowing", isFollowing);
					model.addAttribute("isLiking", isLiking);
				}
				int pStart = 0;
				int pOffset = 10;
				boolean pAscending = false;
				int cStart = 0;
				int cOffset = 3;
				boolean cAscending = false;
				List<CommentBo> parentCommentBos = this.getParentCommentBos(id, pStart, pOffset, pAscending, cStart, cOffset, cAscending);
				model.addAttribute("parentCommentBos", parentCommentBos);

				Long parentCountL = commentService.findCountByTopicId(id);
				// assume count less than Integer.MAX_VALUE
				int parentCount = Util.safeLongToInt(parentCountL);
				model.addAttribute("parentCount", parentCount);
				model.addAttribute("topicBo", topicBo);

				TopicGraph topicGraph = topicRepository.findByPropertyValue("topicId", id);
				int numOfUsersWhoLike = getNumOfUsersWhoLike(topicGraph);
		    	long numOfComments = commentService.findCountByTopicId(id);
		    	model.addAttribute("numOfUsersWhoLike", numOfUsersWhoLike);
		    	model.addAttribute("numOfComments", numOfComments);
			}
		}catch(Exception e) {
			if(e instanceof NoSuchTopicException) {
				return "exception/404";
			}else {
				logger.error(e.getMessage(), e);
				throw e; // handle by Spring
			}
		}
		return "topic/view_popup";
	}
	
	@RequestMapping(value = "/topic/like_or_unlike", method = RequestMethod.GET)
	public @ResponseBody String likeOrUnlike(@RequestParam(value="topic_id", required=true) String topicId) {
		String result = Constants.RESULT.FAILURE;
		try{
			if(!isSignedIn()) {
				return Constants.RESULT.UNAUTHORIZED;
			}
			String currentUserId = userService.findCurrentUserId();
			userGraphService.likeOrUnlike(currentUserId, topicId);
			result = Constants.RESULT.SUCCESS;
		}catch(Exception e) {
			logger.error(e.getMessage(), e);
		}
		return result;
	}

	@RequestMapping(value = "/explore", method = RequestMethod.GET)
	public String viewExplorePage(Model model) {
		model.addAttribute("cur", "explore");
		return "topic/explore";
	}
	
	@RequestMapping(value = "/topic/retrieve", method = RequestMethod.GET)
	public @ResponseBody TopicJsonListBo follow(Model model, HttpServletRequest request,
    		@RequestParam(value="category", required=true) String category,
    		@RequestParam(value="has_init_total", required=true) Boolean hasInitTotal,
    		@RequestParam(value="init_total", required=true) Integer initTotal,
    		@RequestParam(value="cur_showing_no", required=true) Integer curShowingNo,
    		@RequestParam(value="offset", required=true) Integer offset,
    		@RequestParam(value="order_by", required=true) String orderBy) throws Exception {
		try {
    		String contextPath = request.getContextPath();
			boolean ascending = false;
			if("ASC".equalsIgnoreCase(orderBy)) {
				ascending = true;
			}
			int start = curShowingNo;
			Long countL = topicService.findCountByCategory(category);
			
			// Catch this exception, we'll see ...
			int count = Util.safeLongToInt(countL);

			int total = initTotal;
			if(!hasInitTotal) {
				total = count;
			}
			int balance = 0;
			if(hasInitTotal && !ascending) {
				balance = count - initTotal;
				start += balance;
			}
			
			return populateTopicJsonList(contextPath, category, start, offset, ascending, total);
		} catch (Exception e) {
			if(e instanceof IllegalArgumentException) {
				// We'll see ...
			}
			logger.error(e.getMessage(), e);
			throw e; // handle by Spring
		}
	}
	
	private TopicJsonListBo populateTopicJsonList(String contextPath, String category, int start, int offset, boolean ascending, int total) throws ZzzzzzException {
		List<TopicJsonBo> topicJsonBos = new ArrayList<TopicJsonBo>();
		List<SimpleTopicBo> simpleTopicBos = topicService.findAllSimpleBos(category, start, offset, ascending);
		for(SimpleTopicBo simpleTopicBo : simpleTopicBos) {
			TopicJsonBo TopicJsonBo = new TopicJsonBo(getTopicHtml(simpleTopicBo, contextPath));
			topicJsonBos.add(TopicJsonBo);
		}
    	TopicJsonListBo topicJsonListBo = new TopicJsonListBo(topicJsonBos, total);
    	return topicJsonListBo;
	}
	
    private String getTopicHtml(SimpleTopicBo simpleTopicBo, String contextPath) throws ZzzzzzException {
    	String topicId = simpleTopicBo.getTopicId();
		TopicGraph topicGraph = topicRepository.findByPropertyValue("topicId", topicId);
    	String category = simpleTopicBo.getCategory();
    	String viewTopicURL = contextPath + "/topic_popup/" + topicId;
    	String html = "<div class=\"topic ui-absolute\"><div class=\"topic-holder\">";
    	if(Constants.TOPIC_CATEGORY.VIDEO.equalsIgnoreCase(category)) {
        	String thumbnailURL = contextPath + "/image/" + simpleTopicBo.getThumbnailId();
        	String thumbnailHeight = simpleTopicBo.getThumbnailHeight();
//    		html += "<a class=\"topic-image-link\" href=\"" + viewTopicURL + "\"><img src=\"" + thumbnailURL + "\" style=\"height: " + thumbnailHeight + "px;\"></a>";
    		html += "<a data-popup=\"{ &quot;popup_width&quot; : 980, &quot;src&quot; : &quot;" + viewTopicURL + "&quot; }\" href=\"#show_popup\" class=\"topic-image-link\"><img src=\"" + thumbnailURL + "\" style=\"height: " + thumbnailHeight + "px;\"></a>";
        }
    	String description = simpleTopicBo.getDescription();
    	html += "<div class=\"description\">";
    	if(Constants.TOPIC_CATEGORY.BOOK.equalsIgnoreCase(category)) {
//    		html += "<a href=\"" + viewTopicURL + "\"><span>" + description + "</span></a>";
    		html += "<div class=\"box\"><a data-popup=\"{ &quot;popup_width&quot; : 980, &quot;src&quot; : &quot;" + viewTopicURL + "&quot; }\" href=\"#show_popup\"><span>" + description + "</span></a></div>";
    	}else {
    		html += "<span>" + description + "</span>";
    	}
    	html += "</div>";
    	String userId = simpleTopicBo.getUserId();
    	User user = userService.findById(userId);
    	String profileURL = contextPath + "/profile/" + userId;
    	String userName = user.getUsername();
    	String userThumbnailURL = contextPath + "/res/images/default_portrait.png";
    	String userThumbnailId = user.getThumbnailId();
		if(Validator.isNotNull(userThumbnailId)) {
			userThumbnailURL = contextPath + "/image/" + user.getThumbnailId();
		}
    	
    	String displayDate = simpleTopicBo.getDisplayDate();

    	html += "<div class=\"attribution clearfix\">";
    	html += "<a href=\"" + profileURL + "\" class=\"lfloat mrs\"><img class=\"thumb thumb_32\" src=\"" + userThumbnailURL + "\"></a>";
    	html += "<div class=\"clearfix ui-overflow-hidden\">";
    	html += "<div class=\"ui-overflow-hidden\">";
    	html += "<div class=\"ui-inline-block\">";
    	html += "<div class=\"ui-inline-block ui-inline-block-middle\" style=\"height: 32px;\"></div>";
    	html += "<div class=\"ui-inline-block ui-inline-block-middle\">";
    	html += "<div class=\"fwb\"><a class=\"black\" href=\"" + profileURL + "\">" + userName + "</a></div>";
    	html += "<div class=\"fcl\">" + displayDate + "</div>";
    	html += "</div></div></div></div></div>";
    	
    	if(isSignedIn()) {
			User currentUser = userService.findCurrentUser();
			UserGraph currentUserGraph = userRepository.findByPropertyValue("userId", currentUser.getUserId());
    		html += "<div class=\"action-overlay\"><div class=\"action-buttons\">";
    		if(currentUser.getUserId().equalsIgnoreCase(simpleTopicBo.getUserId())) {
    			String editURL = contextPath + "/topic/edit/" + simpleTopicBo.getTopicId();
    			html += "<a href=\"" + editURL + "\">Edit</a>";
    		}else {
    			boolean isLiking = userGraphService.hasRelationship(currentUserGraph, topicGraph, Rels.LIKE.name());
    			String likeOrUnlikeClassName = "like";
    			if(isLiking) {
    				likeOrUnlikeClassName = "unlike";
    			}
    			html += "<a href=\"#like_unlike_link\" data-topic-id=\"" + topicId + "\" class=\"" + likeOrUnlikeClassName + "\">";
    			html += "<span class=\"z-like\">Like</span><span class=\"z-unlike\">Unlike</span></a>";
    		}
    		html += "</div></div>";
    	}
    	int numOfUsersWhoLike = getNumOfUsersWhoLike(topicGraph);
    	long numOfComments = commentService.findCountByTopicId(topicId);
    	html += "<div class=\"state\">";
    	if(numOfUsersWhoLike > 0) {
    		html += "<span class=\"like-button fcl\">" + numOfUsersWhoLike + "</span>";
    	}
    	if(numOfComments > 0) {
    		html += "<span class=\"mls comment-button fcl\">" + numOfComments + "</span>";
    	}
    	html += "</div>";
    	html += "</div></div>";
    	return html;
    }
    
	@RequiresUser
	@RequestMapping(value = "/comment/add", method = RequestMethod.POST)
	public @ResponseBody CommentAddJsonBo addComment(Model model, HttpServletRequest request,
			@RequestParam(value="topic_id", required=true) String topicId,
			@RequestParam(value="content", required=true) String content) throws Exception {
		try{
    		User currentUser = userService.findCurrentUser();
			Comment comment = commentService.add(currentUser.getUserId(), topicId, StringPool.BLANK, URLDecoder.decode(content,"utf-8"));
    		String contextPath = request.getContextPath();

			CommentBo parentCommentBo = new CommentBo(comment);
			parentCommentBo.setUserName(currentUser.getUsername());
			parentCommentBo.setUserThumbnailId(currentUser.getThumbnailId());
			
			int cStart = 0;
			int cOffset = 3;
			boolean cAscending = false;
			int childCount = 0;
			parentCommentBo.setChildCount(childCount);
			String html = this.getParentCommentHtml(topicId, parentCommentBo, contextPath, currentUser, cStart, cOffset, cAscending);
			CommentAddJsonBo commentAddJsonBo = new CommentAddJsonBo(html, topicId);
			return commentAddJsonBo;
		}catch(Exception e) {
			logger.error(e.getMessage(), e);
			throw e;
		}
	}

	@RequiresUser
	@RequestMapping(value = "/comment/reply", method = RequestMethod.POST)
	public @ResponseBody CommentReplyJsonBo addReply(Model model, HttpServletRequest request,
			@RequestParam(value="topic_id", required=true) String topicId,
			@RequestParam(value="parent_id", required=true) String parentId,
			@RequestParam(value="content", required=true) String content) throws Exception {
		try{
    		User currentUser = userService.findCurrentUser();
    		String contextPath = request.getContextPath();
			
			Comment comment = commentService.add(currentUser.getUserId(), topicId, parentId, URLDecoder.decode(content,"utf-8"));

			CommentBo childCommentBo = new CommentBo(comment);
			childCommentBo.setUserName(currentUser.getUsername());
			childCommentBo.setUserThumbnailId(currentUser.getThumbnailId());
			
			String html = this.getChildCommentHtml(childCommentBo, contextPath, currentUser);
			CommentReplyJsonBo commentReplyJsonBo = new CommentReplyJsonBo(html, topicId, parentId);
			return commentReplyJsonBo;
		}catch(Exception e) {
			logger.error(e.getMessage(), e);
			throw e;
		}
	}

	@RequiresUser
	@RequestMapping(value = "/comment/delete", method = RequestMethod.GET)
	public @ResponseBody String deleteComment(Model model, HttpServletRequest request,
			@RequestParam(value="comment_id", required=true) String commentId, 
			@RequestParam(value="is_parent", required=true) Boolean isParent) throws Exception {
		try {
			commentService.delete(commentId);
			if(isParent) {
				List<Comment> childComments = commentService.findByParentId(commentId);
				for(Comment cc : childComments) {
					commentService.delete(cc.getCommentId());
				}
			}
			return commentId;
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			throw e;
		}
	}

	@RequestMapping(value = "/comment/retrieve", method = RequestMethod.GET)
    public @ResponseBody CommentJsonListBo retrieveComments(Model model, HttpServletRequest request, 
    		@RequestParam(value="topic_id", required=true) String topicId, 
    		@RequestParam(value="init_total", required=true) Integer initTotal, 
    		@RequestParam(value="cur_showing_no", required=true) Integer curShowingNo, 
    		@RequestParam(value="offset", required=true) Integer offset, 
    		@RequestParam(value="order_by", required=true) String orderBy) throws Exception {
		try {
    		String contextPath = request.getContextPath();
			boolean ascending = false;
			if("ASC".equalsIgnoreCase(orderBy)) {
				ascending = true;
			}
			int start = curShowingNo;
			Long countL = commentService.findCountByTopicId(topicId);
			
			// Catch this exception, we'll see ...
			int count = Util.safeLongToInt(countL);
			int balance = 0;
			if(!ascending) {
				balance = count - initTotal;
				start += balance;
			}
			User currentUser = userService.findCurrentUser();
			int cStart = 0;
			int cOffset = 3;
			boolean cAscending = false;
			return populateParentCommentJsonList(currentUser, contextPath, topicId, start, offset, ascending, cStart, cOffset, cAscending);
		} catch (Exception e) {
			if(e instanceof IllegalArgumentException) {
				// We'll see ...
			}
			logger.error(e.getMessage(), e);
			throw e; // handle by Spring
		}
	}
	
	@RequestMapping(value = "/reply/retrieve", method = RequestMethod.GET)
    public @ResponseBody CommentJsonListBo retrieveReplies(Model model, HttpServletRequest request, 
    		@RequestParam(value="parent_id", required=true) String parentId, 
    		@RequestParam(value="init_total", required=true) Integer initTotal, 
    		@RequestParam(value="cur_showing_no", required=true) Integer curShowingNo, 
    		@RequestParam(value="offset", required=true) Integer offset, 
    		@RequestParam(value="order_by", required=true) String orderBy) throws Exception {
		try {
    		String contextPath = request.getContextPath();
			boolean ascending = false;
			if("ASC".equalsIgnoreCase(orderBy)) {
				ascending = true;
			}
			int start = curShowingNo;
			Long childCountL = commentService.findCountByParentId(parentId);
			
			// Catch this exception, we'll see ...
			int childCount = Util.safeLongToInt(childCountL);
			int balance = 0;
			if(!ascending) {
				balance = childCount - initTotal;
				start += balance;
			}
			User currentUser = userService.findCurrentUser();
			return populateChildJsonList(currentUser, contextPath, parentId, start, offset, ascending);
		} catch (Exception e) {
			if(e instanceof IllegalArgumentException) {
				// We'll see ...
			}
			logger.error(e.getMessage(), e);
			throw e; // handle by Spring
		}
	}
	
	private CommentJsonListBo populateParentCommentJsonList(User currentUser, String contextPath, String topicId, int pStart, int pOffset, boolean pAscending, int cStart, int cOffset, boolean cAscending) throws ZzzzzzException {
		List<CommentJsonBo> commentJsonBos = new ArrayList<CommentJsonBo>();
    	List<CommentBo> parentCommentBos = getParentCommentBos(topicId, pStart, pOffset, pAscending, cStart, cOffset, cAscending);
    	
    	for(CommentBo parentCommentBo : parentCommentBos) {
			String html = getParentCommentHtml(topicId, parentCommentBo, contextPath, currentUser, cStart, cOffset, cAscending);
		    CommentJsonBo commentJsonBo = new CommentJsonBo(html);
		    commentJsonBos.add(commentJsonBo);
    	}
    	CommentJsonListBo commentJsonListBo = new CommentJsonListBo(commentJsonBos);
    	return commentJsonListBo;
	}
	
	private CommentJsonListBo populateChildJsonList(User currentUser, String contextPath, String parentId, int cStart, int cOffset, boolean cAscending) throws ZzzzzzException {
		List<CommentJsonBo> commentJsonBos = new ArrayList<CommentJsonBo>();
    	List<CommentBo> childCommentBos = getChildCommentBos(parentId, cStart, cOffset, cAscending);
    	
    	for(CommentBo childCommentBo : childCommentBos) {
			String html = getChildCommentHtml(childCommentBo, contextPath, currentUser);
		    CommentJsonBo commentJsonBo = new CommentJsonBo(html);
		    commentJsonBos.add(commentJsonBo);
    	}
    	CommentJsonListBo commentJsonListBo = new CommentJsonListBo(commentJsonBos);
    	return commentJsonListBo;
	}
	
    private String getParentCommentHtml(String topicId, CommentBo parentCommentBo, String contextPath, User currentUser, int cStart, int cOffset, boolean cAscending) throws ZzzzzzException {
    	String parentCommentId = parentCommentBo.getCommentId();
		String content = parentCommentBo.getContent();
		String displayDate = parentCommentBo.getDisplayDate();
		String actorId = parentCommentBo.getUserId();
		
		String userName = parentCommentBo.getUserName();

		String userThumbnailURL = contextPath + "/res/images/default_portrait.png";
		String userThumbnailId = parentCommentBo.getUserThumbnailId();
		if(Validator.isNotNull(userThumbnailId)) {
			userThumbnailURL = contextPath + "/image/" + parentCommentBo.getUserThumbnailId();
		}
    	
		String profileURL = contextPath + "/profile/" + actorId;
		String html = "<li data-comment-is-parent=\"true\" data-comment-id=\"" + parentCommentId + "\" class=\"comment-entry level1\">" + 
			"<div class=\"comment-entry-inner\">" + 
			"<a href=\"" + profileURL + "\"><img class=\"avatar\" src=\"" + userThumbnailURL +"\"/></a>" + 
			"<div class=\"comment-content-wrap\">" + 
			"<div class=\"comment-content\" data-comment-dom-id=\"entry-content\">" + 
			"<a class=\"fwb\" href=\"" + profileURL + "\">" + userName + "</a>" + " - <span class=\"fcl\">" + displayDate + "</span>" + 
			"<div>" + content + "</div>";
		
		if(isSignedIn() && currentUser != null) {
			boolean isOwner = (actorId.equalsIgnoreCase(currentUser.getUserId())) ? true : false;
			html += "<div><a href=\"#add_reply_link\">Reply</a></div>";
			if(isOwner) {
				html += "<div data-comment-dom-id=\"entry-options\" class=\"options\" style=\"display: none;\"><a href=\"#\"></a></div>";
			}	
		}

		html += "</div>";
		
		html += "<div class=\"comment-reply-container\" data-loadmore-dom-id=\"loadmore-container\">" + 
			"<ul data-comment-dom-id=\"child-entry-container\" data-loadmore-dom-id=\"items-container\">";
    	
		int childCount = parentCommentBo.getChildCount();
		List<CommentBo> childCommentBos = this.getChildCommentBos(parentCommentId, cStart, cOffset, cAscending);
		for(CommentBo childCommentBo : childCommentBos) {
			html += getChildCommentHtml(childCommentBo, contextPath, currentUser);
		}
		
		html += "</ul>";
		html += "<div ";
		if(childCount <= childCommentBos.size()) {
			html += "style=\"display: none;\"";
		}
		html += "data-loadmore-dom-id=\"view-more-container\" class=\"comment-children-more\"><a data-loadmore=\"{ &quot;retrieve_url&quot; : &quot;" + contextPath + "/reply/retrieve&quot;, &quot;retrieve_params&quot; : {&quot;parent_id&quot; : &quot;" + parentCommentId + "&quot;, &quot;offset&quot; : " + cOffset + ", &quot;order_by&quot; : &quot;DESC&quot;, &quot;cur_showing_no&quot; : " + childCommentBos.size() + ", &quot;init_total&quot; : " + childCount + "}, &quot;no_results_msg&quot; : &quot;&quot;, &quot;no_more_results_msg&quot; : &quot;&quot;, &quot;error_msg&quot; : &quot;This stream is unavailable at this time. Please try again soon.&quot; }\" href=\"#\">View <span data-loadmore-dom-id=\"unshown-status\">" + (childCount - childCommentBos.size()) + "</span> more <span class=\"arrow arrow-down arrow-down-lightblue arrow-down-small\"></span></a></div>";
		html += "<div style=\"display: none;\" align=\"center\" data-loadmore-dom-id=\"loading-status\"><img src=\"" + contextPath + "/res/images/common/loading.gif\"/></div>";
		html += "<div style=\"display: none;\" align=\"center\" data-loadmore-dom-id=\"load-msg\"></div>";
		if(isSignedIn()) {
			html += getReplyFormHtml(contextPath, currentUser, topicId);
		}
		html += "</div></div></div></li>";
    	
    	return html;
    }
    
    private String getChildCommentHtml(CommentBo commentBo, String contextPath, User currentUser) {
    	String commentId = commentBo.getCommentId();
		String content = commentBo.getContent();
		String displayDate = commentBo.getDisplayDate();
		String actorId = commentBo.getUserId();
		String userName = commentBo.getUserName();
		String profileURL = contextPath + "/profile/" + actorId;

		String userThumbnailURL = contextPath + "/res/images/default_portrait.png";
		String userThumbnailId = commentBo.getUserThumbnailId();
		if(Validator.isNotNull(userThumbnailId)) {
			userThumbnailURL = contextPath + "/image/" + commentBo.getUserThumbnailId();
		}
		
    	String html = "<li data-comment-id=\"" + commentId + "\" data-comment-is-parent=\"false\" class=\"comment-entry level2\">" + 
			"<div class=\"comment-entry-inner\">" + 
			"<a href=\"" + profileURL + "\"><img src=\"" + userThumbnailURL +"\" class=\"avatar\"/></a>" + 
			"<div class=\"comment-content-wrap\">" + 
			"<div class=\"comment-content\" data-comment-dom-id=\"entry-content\">" + 
			"<a href=\"" + profileURL + "\" class=\"fwb\">" + userName + "</a> - <span class=\"fcl\">" + displayDate + "</span>" + 
			"<div>" + content + "</div>";
    	
    	if(isSignedIn() && currentUser != null) {
			html += "<div><a href=\"#add_reply_link\">Reply</a></div>";
			boolean isOwner = (actorId.equalsIgnoreCase(currentUser.getUserId())) ? true : false;;
			if(isOwner) {
				html += "<div data-comment-dom-id=\"entry-options\" class=\"options\" style=\"display: none;\"><a href=\"#\"></a></div>";
			}
    	}
		
		html += "</div></div></div></li>";
		return html;
    }
    
    private String getReplyFormHtml(String contextPath, User currentUser, String topicId) {
    	String currentUserThumbnailURL = contextPath + "/res/images/default_portrait.png";
    	if(currentUser != null) {
    		String currentUserThumbnailId = currentUser.getThumbnailId();
    		if(Validator.isNotNull(currentUserThumbnailId)) {
        		currentUserThumbnailURL = contextPath + "/image/" + currentUser.getThumbnailId();
    		}
    	}

    	String html = "<div data-comment-dom-id=\"add-reply-form-container\" class=\"comment-entry level2\" style=\"display: none;\">" + 
			"<div class=\"comment-entry-inner\">" + 
			"<img data-comment-dom-id=\"avatar\" src=\"" + currentUserThumbnailURL + "\" class=\"avatar\"/>" + 
			"<div style=\"margin: 0 0 8px 58px; min-height: 50px;\">" + 
			"<form data-comment-dom-id=\"add-reply-form\" action=\"" + contextPath + "/comment/reply\" method=\"post\">" + 
			"<input type=\"hidden\" name=\"topicId\" value=\"" + topicId + "\"/>" + 
			"<div class=\"comment-textarea-outer\">" + 
			"<textarea data-comment-dom-id=\"add-reply-textarea\" placeholder=\"Add a reply...\" style=\"height: 28px;\"></textarea>" + 
			"</div>" + 
			"<div align=\"right\" class=\"mts\"><input type=\"submit\" class=\"btn style1 small\" value=\"Comment\" /></div>" + 
			"</form>" + 
			"</div></div></div>";
    	return html;
    }
    
	private List<CommentBo> getParentCommentBos(String topicId, int pStart, int pOffset, boolean pAscending, int cStart, int cOffset, boolean cAscending) throws ZzzzzzException {
		List<CommentBo> parentCommentBos = new ArrayList<CommentBo>();
		List<Comment> parentComments = commentService.findByTopicId(topicId, pStart, pOffset, pAscending);
		for(Comment parentComment : parentComments) {
			String parentCommentId = parentComment.getCommentId();
			String userId = parentComment.getUserId();
			User actor = userService.findById(userId);
			CommentBo parentCommentBo = new CommentBo(parentComment);
			parentCommentBo.setUserName(actor.getUsername());
			parentCommentBo.setUserThumbnailId(actor.getThumbnailId());
			List<CommentBo> childCommentBos = getChildCommentBos(parentCommentId, cStart, cOffset, cAscending);
			parentCommentBo.setChildCommentBos(childCommentBos);

			Long childCountL = commentService.findCountByParentId(parentCommentId);
			// assume count less than Integer.MAX_VALUE
			int childCount = Util.safeLongToInt(childCountL);
			parentCommentBo.setChildCount(childCount);
			parentCommentBos.add(parentCommentBo);
		}
		return parentCommentBos;
	}
	
	private List<CommentBo> getChildCommentBos(String parentId, int cStart, int cOffset, boolean cAscending) throws ZzzzzzException {
		List<Comment> childComments = commentService.findByParentId(parentId, cStart, cOffset, cAscending);
		List<CommentBo> childCommentBos = new ArrayList<CommentBo>();
		for(Comment childComment : childComments) {
			CommentBo childCommentBo = new CommentBo(childComment);
			String userId = childComment.getUserId();
			User actor = userService.findById(userId);
			childCommentBo.setUserName(actor.getUsername());
			childCommentBo.setUserThumbnailId(actor.getThumbnailId());
			childCommentBos.add(childCommentBo);
		}
		return childCommentBos;
	}
	
	private int getNumOfUsersWhoLike(TopicGraph startTopicGraph) {
		int numOfUsersWhoLike = 0;
		if(startTopicGraph != null) {
			Iterable<UserGraph> allUsersWhoLikeIterable = startTopicGraph.getAllUsersWhoLike();
			Collection<UserGraph> allUsersWhoLikeCollection = IteratorUtil.asCollection(allUsersWhoLikeIterable);
			for(int i=0; i<allUsersWhoLikeCollection.size(); i++) {
				numOfUsersWhoLike += 1;
			}
		}
		return numOfUsersWhoLike;
	}
    
	private static final Logger logger = LoggerFactory.getLogger(TopicController.class);
	
}
